Hi there! This notebook covers the basic ptplot interface for making plots. You'll see how easy it is to generate an interactive, beautiful-looking plot showing player-tracking data, as well as some knobs you can tweak to adjust things just to how you want.
We'll start with some simple imports.
import numpy as np
import pandas as pd
from ptplot import plotting
We'll pick a single play to look at, just for demonstration purposes. I've somewhat-randomly chosen this Cleveland Browns reverse + WR pass from the 2018 season, which has player-tracking data available for it from Adam Sonty's ngs_highlights repo (specifically, the file used is located here).
# Data file copied locally to ensure the demo works even if the ngs_highlights_repo is taken down
player_tracking_data = pd.read_csv(
"2018_CLE_2018122305_1246.tsv",
sep="\t", parse_dates=["time"]
)
player_tracking_data.head().T
| 0 | 1 | 2 | 3 | 4 | |
|---|---|---|---|---|---|
| gameId | 2018122305 | 2018122305 | 2018122305 | 2018122305 | 2018122305 |
| playId | 1246 | 1246 | 1246 | 1246 | 1246 |
| playType | play_type_pass | play_type_pass | play_type_pass | play_type_pass | play_type_pass |
| season | 2018 | 2018 | 2018 | 2018 | 2018 |
| seasonType | REG | REG | REG | REG | REG |
| week | 16 | 16 | 16 | 16 | 16 |
| preSnapHomeScore | 7 | 7 | 7 | 7 | 7 |
| preSnapVisitorScore | 0 | 0 | 0 | 0 | 0 |
| playDirection | right | right | right | right | right |
| quarter | 2 | 2 | 2 | 2 | 2 |
| gameClock | 08:45 | 08:45 | 08:45 | 08:45 | 08:45 |
| down | 1 | 1 | 1 | 1 | 1 |
| yardsToGo | 10 | 10 | 10 | 10 | 10 |
| yardline | CLE 30 | CLE 30 | CLE 30 | CLE 30 | CLE 30 |
| yardlineSide | CLE | CLE | CLE | CLE | CLE |
| yardlineNumber | 30 | 30 | 30 | 30 | 30 |
| absoluteYardlineNumber | 40 | 40 | 40 | 40 | 40 |
| possessionFlag | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 |
| homeTeamFlag | 1.00 | 1.00 | 1.00 | 1.00 | 1.00 |
| teamAbbr | CLE | CLE | CLE | CLE | CLE |
| frame | 0 | 1 | 2 | 3 | 4 |
| displayName | Kevin Zeitler | Kevin Zeitler | Kevin Zeitler | Kevin Zeitler | Kevin Zeitler |
| esbId | ZEI626612 | ZEI626612 | ZEI626612 | ZEI626612 | ZEI626612 |
| gsisId | 00-0029592 | 00-0029592 | 00-0029592 | 00-0029592 | 00-0029592 |
| jerseyNumber | 70.00 | 70.00 | 70.00 | 70.00 | 70.00 |
| nflId | 2532980.00 | 2532980.00 | 2532980.00 | 2532980.00 | 2532980.00 |
| position | G | G | G | G | G |
| positionGroup | OL | OL | OL | OL | OL |
| time | 2018-12-23 18:53:50.500000 | 2018-12-23 18:53:50.600000 | 2018-12-23 18:53:50.700000 | 2018-12-23 18:53:50.800000 | 2018-12-23 18:53:50.900000 |
| x | 35.35 | 35.35 | 35.35 | 35.36 | 35.38 |
| y | 23.13 | 23.12 | 23.12 | 23.12 | 23.12 |
| s | 0.01 | 0.01 | 0.00 | 0.01 | 0.06 |
| o | 316.63 | 317.88 | 319.24 | 321.66 | 325.08 |
| dir | 281.07 | 279.54 | 279.34 | 89.97 | 93.66 |
| event | NaN | NaN | NaN | NaN | NaN |
| playDescription | (8:45) J.Landry pass deep right to B.Perriman ... | (8:45) J.Landry pass deep right to B.Perriman ... | (8:45) J.Landry pass deep right to B.Perriman ... | (8:45) J.Landry pass deep right to B.Perriman ... | (8:45) J.Landry pass deep right to B.Perriman ... |
Let's start by making a simple plot that shows player positions at a certain frame. Frames where interesting stuff happens are actually tagged by the event column, so we'll choose the moment of time when the pass occurred:
fig = plotting.plot_positions(
player_tracking_data[player_tracking_data["event"] == "pass_forward"],
"x", "y"
)
fig.show()
That's nice, but all the players look the same, which isn't very helpful. The good news is that it's pretty easy to make things clearer. First we'll add a column to the DataFrame to denote which rows contain the ball, then we'll use that column + others to add team and player information:
player_tracking_data["is_ball"] = (player_tracking_data["displayName"] == "ball")
fig = plotting.plot_positions(
player_tracking_data[player_tracking_data["event"] == "pass_forward"], "x", "y",
uniform_number="jerseyNumber",
home_away_identifier="homeTeamFlag",
hover_text="displayName",
ball_identifier="is_ball",
team_abbreviations="teamAbbr"
)
fig.show()
Much better. In addition to the addition of team colors and player numbers, note that mousing over the markers shows the player's name too. You can also use the Plotly controls on the top right of the chart to do things like zoom in, select players, and save a static image.
in addition to making plots of player positions, ptplot can also generate tracks of player positions over the course of the play. The code in the next cell generates those tracks for the entire play:
fig = plotting.plot_tracks(
player_tracking_data, "x", "y", "displayName",
home_away_identifier="homeTeamFlag",
hover_text="displayName",
ball_identifier="is_ball",
team_abbreviations="teamAbbr"
)
fig.show()
Again you'll see similar interactivity as for the player positions plot.
Now, if you want both the positions and player tracks, it's possible to combine these two figures by re-using the figure object that ptplot's plotting functions return:
# Just look between the snap and the pass, to make the tracks clearer
snap_frame = player_tracking_data[player_tracking_data["event"] == "ball_snap"]["frame"].unique()[0]
pass_forward_frame = player_tracking_data[player_tracking_data["event"] == "pass_forward"]["frame"].unique()[0]
fig = plotting.plot_tracks(
player_tracking_data[player_tracking_data["frame"].between(snap_frame, pass_forward_frame)],
"x", "y", "displayName",
home_away_identifier="homeTeamFlag",
hover_text="displayName",
ball_identifier="is_ball",
team_abbreviations="teamAbbr"
)
plotting.plot_positions(
player_tracking_data[player_tracking_data["event"] == "pass_forward"], "x", "y",
uniform_number="jerseyNumber",
home_away_identifier="homeTeamFlag",
hover_text="displayName",
ball_identifier="is_ball",
team_abbreviations="teamAbbr",
fig=fig
)
fig.show()
Lastly, ptplot comes with support for both horizontal and vertical field orientations. Horizontal views tend to be better for computers, which tend towards widescreen displays, while vertical views scroll better on phones.
# Same plot as before, but now vertically oriented
vertical_fig = plotting.plot_tracks(
player_tracking_data[player_tracking_data["frame"].between(snap_frame, pass_forward_frame)],
"y", "x", "displayName",
home_away_identifier="homeTeamFlag",
hover_text="displayName",
ball_identifier="is_ball",
team_abbreviations="teamAbbr",
fig="nfl_vertical"
)
plotting.plot_positions(
player_tracking_data[player_tracking_data["event"] == "pass_forward"], "y", "x",
uniform_number="jerseyNumber",
home_away_identifier="homeTeamFlag",
hover_text="displayName",
ball_identifier="is_ball",
team_abbreviations="teamAbbr",
fig=vertical_fig
)
vertical_fig.update_layout(width=250, height=550)
vertical_fig.show()